package info.sybian.web.view;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;

import objot.util.Err;

/**
 * LGPL V3
 * 
 * @author CrazyBird
 */
public final class ViewTemplate {
	private ViewEngine eng;
	private String expr;
	/** -1: not index, -2: unchecked */
	int index = -2;
	/** 0: escape, >0: no escape no false, <0: not */
	private int mode;
	private int out;
	private String innerPre;
	private ViewTemplate inner;
	private String nextPre = "";
	private ViewTemplate next;
	private int cEnd1;

	private static final int INIT = 0;
	private static final int EXPR = 1;
	private static final int INNER_PRE = 2;
	private static final int INCLUDE = 3;
	private static final int NEXT_PRE = 4;
	private static final Random RANDOM = new Random();

	int init(ViewEngine eng_, byte[] bs, int begin, int end1, char[] cs, int cBegin, boolean outest) throws Exception {
		if(begin >= end1)
			throw new IllegalArgumentException("Empty template.");
		eng = eng_;
		if(cs == null)
			cs = new char[end1 - begin];
		int state = INIT;
		int x = begin, y = cBegin, c, exB = 0, inpB = 0, nepB = 0;
		while(x < end1) {
			if((c = bs[x++]) >= 0)
				cs[y++] = (char)c;
			else if((c & 0xe0) == 0xc0)
				c = cs[y++] = (char)((c & 31) << 6 | bs[x++] & 63);
			else
				c = cs[y++] = (char)((c & 15) << 12 | (bs[x++] & 63) << 6 | bs[x++] & 63);
			if(c == '&' && x < end1)
				if((c = bs[x]) == '[' || c == ']') {
					cs[y - 1] = (char)c;
					x++;
					continue;
				}

			switch(state) {
				case INIT:
					state = c == '[' ? EXPR : NEXT_PRE;
					if(state == EXPR)
						exB = y;
					else
						nepB = y - 1;
					break;
				case EXPR:
					if(c == ',') {
						int b = exB;
						if(cs[b] == '=')
							b += mode = 1;
						else if(cs[b] == '!')
							b -= mode = -1;
						for(; cs[b] == '^'; out++, b++)
							;
						expr = string(cs, b, y - 1);
						state = INNER_PRE;
						inpB = y;
					}
					else if(c == '/') {
						int b = exB;
						if(cs[b] == '=')
							b += mode = 1;
						else if(cs[b] == '!')
							b -= mode = -1;
						for(; cs[b] == '^'; out++, b++)
							;
						expr = string(cs, b, y - 1);
						state = INCLUDE;
						exB = y;
					}
					else if(c == '[')
						throw new IllegalArgumentException("Template format error: need ',' or '/'.");
					else if(c == ']') {
						int b = exB;
						if(cs[b] == '=')
							b += mode = 1;
						for(; cs[b] == '^'; out++, b++)
							;
						expr = string(cs, b, y - 1);
						state = NEXT_PRE;
						nepB = y;
					}
					break;
				case INNER_PRE:
					if(c == '[') {
						innerPre = string(cs, inpB, y - 1);
						inner = new ViewTemplate();
						x = inner.init(eng, bs, x - 1, end1, cs, y - 1, false);
						y = inner.cEnd1;
						state = NEXT_PRE;
						nepB = y;
					}
					else if(c == ']') {
						innerPre = string(cs, inpB, y - 1);
						state = NEXT_PRE;
						nepB = y;
					}
					break;
				case INCLUDE:
					if(c == '[')
						throw new IllegalArgumentException("Template format error: need ']'");
					else if(c == ']') {
						innerPre = "";
						inner = eng.template(string(cs, exB, y - 1));
						state = NEXT_PRE;
						nepB = y;
					}
					break;
				case NEXT_PRE:
					if(c == '[') {
						nextPre = string(cs, nepB, y - 1);
						next = new ViewTemplate();
						x = next.init(eng, bs, x - 1, end1, cs, y - 1, outest);
						cEnd1 = next.cEnd1;
						return x;
					}
					else if(c == ']') {
						if(outest)
							throw new IllegalArgumentException("Template format error: illegal ']'.");
						nextPre = string(cs, nepB, y - 1);
						cEnd1 = y;
						return x;
					}
					break;
			}
		}
		if(!outest || state != NEXT_PRE)
			throw new IllegalArgumentException("Template format error: should be end with ']'.");
		nextPre = string(cs, nepB, y);
		cEnd1 = y;
		return x;
	}

	private static String string(char[] cs, int begin, int end1) {
		return begin == end1 ? "" : new String(cs, begin, end1 - begin);
	}

	public static void merge(ViewTemplate t, Object[] s, int x, StringBuilder re) {
		for(; t != null; t = t.next)
			if(t.expr == null)
				re.append(t.nextPre);
			else {
				if(t.innerPre == null)
					if("$ra".equals(t.expr))
						re.append(RANDOM.nextInt() >>> 1);
					else
						format(t.mergeExpr(s, x), t.mode <= 0, re);
				else {
					Object v = t.mergeExpr(s, x);
					if(t.mode < 0)
						t.mergeInner(s, x, v, false, re);
					else if(t.mode == 0 && v instanceof Collection<?>)
						for(Object u : (Collection<?>)v)
							t.mergeInner(s, x, u, true, re);
					else if(t.mode == 0 && v.getClass().isArray()) {
						int len = Array.getLength(v);
						for(int i = 0; i < len; i++)
							t.mergeInner(s, x, Array.get(v, i), true, re);
					}
					else
						t.mergeInner(s, x, v, true, re);
				}
				re.append(t.nextPre);
			}
	}

	private Object mergeExpr(Object[] s, int x) {
		x -= out;
		if(x < 0)
			return null;
		Object v = s[x];
		if(expr.length() == 0)
			return v;
		if(v instanceof Map<?, ?>)
			return ((Map<?, ?>)v).get(expr);
		else if(index != -1 && v instanceof List<?>) {
			if(index == -2) {
				index = -1;
				x = 0;
				for(int i = 0; i < expr.length(); i++) {
					int c = expr.charAt(i) - '0';
					if(c < 0 || c > (x < 214748364 ? 9 : x == 214748364 ? 7 : -1))
						return null;
					x = x * 10 + c;
				}
				index = x;
			}
			if(index < ((List<?>)v).size())
				return ((List<?>)v).get(index);
		}
		else if(index != -1 && v.getClass().isArray()) {
			if(index == -2) {
				index = -1;
				x = 0;
				for(int i = 0; i < expr.length(); i++) {
					int c = expr.charAt(i) - '0';
					if(c < 0 || c > (x < 214748364 ? 9 : x == 214748364 ? 7 : -1))
						return null;
					x = x * 10 + c;
				}
				index = x;
			}
			int len = Array.getLength(v);
			if(index < len)
				return Array.get(v, index);
		}
		return null;
	}

	private void mergeInner(Object[] s, int x, Object v, boolean yes, StringBuilder re) {
		if(mode <= 0)
			if(v == null ? yes : //
					yes != (v instanceof String ? ((String)v).length() > 0 //
							: v instanceof Boolean ? (Boolean)v //
									: v instanceof Number ? ((Number)v).longValue() != 0 //
											: v instanceof Collection<?> ? ((Collection<?>)v).size() > 0 //
													: v.getClass().isArray() ? Array.getLength(v) > 0 //
															: true))
				return;
		re.append(innerPre);
		if(inner != null) {
			s[++x] = v;
			merge(inner, s, x, re);
			s[x] = null;
		}
	}

	private static void format(Object v, boolean esc, StringBuilder re) {
		if(v == null)
			return;
		if(v instanceof Double) {
			double d = (Double)v;
			long l = (long)d;
			if(d == l)
				re.append(l);
			else
				re.append(d);
			return;
		}
		if(v instanceof Number) {
			re.append(((Number)v).longValue());
			return;
		}

		if(esc) {
			String s = v.toString();
			char last = 0, c = 0;
			for(int i = 0; i < s.length(); i++, last = c)
				if((c = s.charAt(i)) == ' ' && last == c)
					re.append((char)160);
				else if(c == '<')
					re.append("&lt;");
				else if(c == '>')
					re.append("&gt;");
				else if(c == '&')
					re.append("&amp;");
				else if(c == '\'')
					re.append("&apos;");
				else if(c == '"')
					re.append("&quot;");
				else
					re.append(c);
		}
		else
			re.append(v.toString());
	}

	static void list(Object r, StringBuilder re) {
		if(r instanceof Err)
			re.append('').append(((Err)r).hint).append('');
		else if(r instanceof Collection<?>)
			for(Object u : (Collection<?>)r) {
				format(u, false, re);
				re.append('');
			}
		else {
			format(r, false, re);
			re.append('');
		}
	}
}
